#include "PageCache.h"

PageCache PageCache::_sInst;

//整体逻辑：
//优先复用已有空闲页→拆分大页→向系统申请新页

// 获取一个包含 k 个连续物理页的 Span
Span* PageCache::NewSpan(size_t k)
{
	assert(k > 0 && k < NPAGES);

	//0.大于128 page的直接向堆申请
	if(k > NPAGES - 1)
	{//直接向操作系统申请 k 个页的连续内存
		void* ptr = SystemAlloc(k);
		//创建一个 Span 对象来管理刚分配的内存页
		Span* span = _spanPool.New();
		//计算并记录该 Span 管理的内存页的起始页 ID
		span->_pageId = (PAGE_ID)ptr >> PAGE_SHIFT;
		span->_n = k;
		//建立页 ID 到 Span 的映射
		_idSpanMap.set(span->_pageId, span);

		return span;
	}

	//1. 优先检查对应大小的桶中是否有空闲 Span
	if(!_spanLists[k].Empty())
	{//如果有空闲 Span（非空），直接弹出头部的 Span 并返回，
	 //这是最高效的情况（直接复用已有资源）
		Span* kSpan = _spanLists[k].PopFront();

		// 建立id和span的映射，方便central cache回收小块内存时，查找对应的span
		//kSpan->_n：表示该 Span 包含的连续页数
		for (PAGE_ID i = 0; i < kSpan->_n; ++i)
		{
			//_idSpanMap[kSpan->_pageId + i] = kSpan;
			_idSpanMap.set(kSpan->_pageId + i, kSpan);
		}

		return kSpan;
	}

	//2. 检查一下后面的桶里面有没有span,若没有对应大小的 Span，
	//则拆分更大的 Span
	for(size_t i = k + 1; i < NPAGES; i++)
	{
		if(!_spanLists[i].Empty())
		{
			// 从i页的桶中取出一个span
			Span* nSpan = _spanLists[i].PopFront();
			// 创建一个新的k页span
			Span* kSpan = new Span;

			// 拆分逻辑：从nSpan的头部切出k页给kSpan
			kSpan->_pageId = nSpan->_pageId;// kSpan的起始页ID与nSpan相同（取头部）
			kSpan->_n = k;// kSpan的页数为k

			// 更新剩余的nSpan：页数减少k，起始页ID后移k页
			nSpan->_pageId += k;
			nSpan->_n -= k;

			// 将剩余的nSpan放回对应大小的桶中
			_spanLists[nSpan->_n].PushFront(nSpan);

			//12两种映射方式分别服务于PageCache 的内存合并和CentralCache 的小块内存回收

			//1.存储nSpan的首位页号跟nSpan映射，方便page cache回收内存时进行的合并查找
			_idSpanMap.set(nSpan->_pageId, nSpan);                  // 首页ID → nSpan
			_idSpanMap.set(nSpan->_pageId + nSpan->_n - 1, nSpan);  // 末页ID → nSpan

			//2.建立id和span的映射，方便central cache回收小块内存时，查找对应的span
			for (PAGE_ID i = 0; i < kSpan->_n; ++i)
			{
    			_idSpanMap.set(kSpan->_pageId + i, kSpan);  // 每个页ID → kSpan
			}

			return kSpan;// 返回拆分出的k页span
		}
	}

	//3. 若没有更大的 Span，则向系统申请新页
	// 向系统申请一个大span（NPAGES-1页，通常为128页）
	Span* bigSpan = new Span;
	//void* ptr = SystemAlloc(NPAGES - 1);调用系统函数分配物理页（如mmap/VirtualAlloc）
	void* ptr = malloc(NPAGES * PAGE_SHIFT);// 向系统申请一大块内存

	bigSpan->_pageId = (PAGE_ID)ptr >> PAGE_SHIFT;// 计算起始页ID（地址/页大小）
	bigSpan->_n = NPAGES - 1;  // 大span包含的页数（如128页）

	// 将大span放入对应大小的桶中
	_spanLists[bigSpan->_n].PushFront(bigSpan);

	// 递归调用自己，此时大span已存在，可拆分出k页
	return NewSpan(k);
}

//根据内存对象的地址，反向查找它所属的Span管理结构
Span* PageCache::MapObjectToSpan(void* obj)
{
	//将内存对象地址转换为其所在的 “页 ID”
	PAGE_ID id = (PAGE_ID)obj >> PAGE_SHIFT;
	//获取页 ID 对应的Span指针
	auto ret = (Span*)_idSpanMap.get(id);

	assert(ret != nullptr);
	return ret;
}

//释放Span管理结构，将其归还到空闲列表中
void PageCache::ReleaseSpanToPageCache(Span* span)
{
	// 大于128 page的直接还给堆
	if(span->_n > NPAGES - 1)
	{
		void* ptr = (void*)(span->_pageId << PAGE_SHIFT);
		SystemFree(ptr);
		//delete span;
		_spanPool.Delete(span);
		return;
	}

	// 与前面的空闲 Span 合并，缓解内存碎片问题
	while(1)
	{
		PAGE_ID prevId = span->_pageId - 1;
		auto ret = (Span*)_idSpanMap.get(prevId);
		if (ret == nullptr)  // 前面没有相邻的Span，退出循环
		{
			break;
		}

		Span* prevSpan = ret;
		if (prevSpan->_isUse == true)  // 前面的Span正在被使用，无法合并
		{
			break;
		}

		if (prevSpan->_n + span->_n > NPAGES-1)  // 合并后超过管理阈值，不合并
		{
			break;
		}

		// 执行合并：将prevSpan合并到当前span中
		span->_pageId = prevSpan->_pageId;  // 更新当前span的起始页ID为prevSpan的起始页
		span->_n += prevSpan->_n;  // 总页数增加prevSpan的页数

		// 从_spanLists中移除被合并的prevSpan（它已不再独立存在）
		_spanLists[prevSpan->_n].Erase(prevSpan);
		_spanPool.Delete(prevSpan);  // 回收prevSpan对象到对象池
	}

	// 向后合并 -- 类似
	while (1)
	{
		PAGE_ID nextId = span->_pageId + span->_n;
		/*auto ret = _idSpanMap.find(nextId);
		if (ret == _idSpanMap.end())
		{
			break;
		}*/

		auto ret = (Span*)_idSpanMap.get(nextId);
		if (ret == nullptr)
		{
			break;
		}

		Span* nextSpan = ret;
		if (nextSpan->_isUse == true)
		{
			break;
		}

		if (nextSpan->_n + span->_n > NPAGES-1)
		{
			break;
		}

		span->_n += nextSpan->_n;

		_spanLists[nextSpan->_n].Erase(nextSpan);
		//delete nextSpan;
		_spanPool.Delete(nextSpan);
	}

	//将合并后的 Span 存入 PageCache 缓存
	_spanLists[span->_n].PushFront(span);  // 按页数存入对应的列表
	span->_isUse = false;  // 标记为未使用（空闲状态）

	// 更新首尾页ID与Span的映射，方便后续合并
	_idSpanMap.set(span->_pageId, span);
	_idSpanMap.set(span->_pageId + span->_n - 1, span);
}